/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package cn.ac.iie.di.ban.news.server.handlers.data;

import cn.ac.iie.di.ban.news.server.check.CheckResult;
import cn.ac.iie.di.ban.news.server.check.JsonChecker;
import cn.ac.iie.di.ban.news.server.check.checkers.MD5Checker;
import cn.ac.iie.di.ban.news.server.exception.NoSuchDataTypeException;
import cn.ac.iie.di.ban.news.server.runtime.RuntimeEnv;
import static cn.ac.iie.di.ban.news.server.runtime.RuntimeEnv.getParamAsStrings;
import cn.ac.iie.di.ban.news.server.speed.SpeedLimiter;
import cn.ac.iie.di.ban.news.server.util.DataSender;
import cn.ac.iie.di.ban.news.server.util.FileAppender;
import cn.ac.iie.di.ban.news.server.util.HttpUtils;
import cn.ac.iie.di.ban.news.server.util.KeyInfoKeeper;
import cn.ac.iie.di.ban.news.server.util.tracer.DataTracer;
import cn.ac.iie.di.commons.httpserver.framework.handler.HandlerI;
import cn.ac.iie.di.commons.speeder.SpeedMonitor;
import cn.ac.iie.di.datadock.rdata.exchange.client.exception.REConnectionException;
import cn.ac.iie.di.datadock.rdata.exchange.client.exception.RESessionException;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.log4j.Logger;
import org.eclipse.jetty.server.Request;

/**
 *
 * @author zwlin
 */
public class DataHandler implements HandlerI {

    private static final Logger logger = Logger.getLogger(DataHandler.class);

    private static final SpeedMonitor monitor;

    static {
        monitor = new SpeedMonitor(SpeedMonitor.PrintType.CUSTOM, 5, 5);
        monitor.setPrinter(new SpeedMonitor.Printer() {
            @Override
            public void print(String key, String value) {
                logger.info("data monitor " + key + " " + value);
            }

            @Override
            public void print(Map<String, String> logMap) {
                for (Map.Entry<String, String> entry : logMap.entrySet()) {
                    String key = entry.getKey();
                    String value = entry.getValue();
                    print(key, value);
                }
            }
        });
        monitor.start();
    }

    private static final MD5Checker md5Checker = new MD5Checker();
    private static SpeedLimiter limiter = null;
    private static DataSender sender = null;
    private static FileAppender fileAppender = null;
    private static KeyInfoKeeper keeper = null;
    private static Boolean online = null;
    private static JsonChecker checker = null;
    private static DataTracer tracer = null;

    public static void init(DataSender sender, FileAppender appender, KeyInfoKeeper keyKeeper, boolean online, JsonChecker checker, DataTracer tracer) {
        DataHandler.setSender(sender);
        DataHandler.setFileAppender(appender);
        DataHandler.setKeeper(keyKeeper);
        DataHandler.setOnline((Boolean) online);
        DataHandler.setChecker(checker);
        DataHandler.setTracer(tracer);
    }

    public static void init(KeyInfoKeeper keyKeeper, boolean online) {
        DataHandler.setKeeper(keyKeeper);
        DataHandler.setOnline((Boolean) online);
    }

    public static SpeedLimiter getLimiter() {
        return limiter;
    }

    public static void setLimiter(SpeedLimiter aLimiter) {
        limiter = aLimiter;
    }

    public static DataSender getSender() {
        return sender;
    }

    public static void setSender(DataSender aSender) {
        sender = aSender;
    }

    public static FileAppender getFileAppender() {
        return fileAppender;
    }

    public static void setFileAppender(FileAppender aFileAppender) {
        fileAppender = aFileAppender;
    }

    public static KeyInfoKeeper getKeeper() {
        return keeper;
    }

    public static void setKeeper(KeyInfoKeeper aKeeper) {
        keeper = aKeeper;
    }

    public static Boolean getOnline() {
        return online;
    }

    public static void setOnline(Boolean aOnline) {
        online = aOnline;
    }

    public static JsonChecker getChecker() {
        return checker;
    }

    public static void setChecker(JsonChecker aChecker) {
        checker = aChecker;
    }

    public static DataTracer getTracer() {
        return tracer;
    }

    public static void setTracer(DataTracer aTracer) {
        tracer = aTracer;
    }

    /**
     * fixme
     *
     * @throws Exception
     */
    @Override
    public void init() throws Exception {
        if (getOnline() == null || getKeeper() == null) {
            throw new Exception("not inited.");
        }
    }

    @Override
    public void execute(Request pRequest, HttpServletRequest request, HttpServletResponse response) throws Exception {

        try {
            long recvTimeMilliSec = System.currentTimeMillis();
            long recvTimeSec = System.currentTimeMillis() / 1000;

            //get key
            String key = request.getParameter("key");

            logger.debug("get request. " + request.getParameterMap());
            //limit speed
            if (getLimiter() != null && !getLimiter().tryGetThrough(key)) {
                logger.warn("speed limited,key: " + key);
                response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                        "1. send too fast, please slow down.");
                return;
            }
            logger.debug(key + " speed limit pased.");
            //check md5
            String md5 = request.getParameter("md5");
            logger.debug("md5: " + md5);
            String content = request.getParameter("content");
            logger.debug("content: " + content);
            CheckResult md5Result = md5Checker.check(content, md5);
            if (!md5Result.correct) {
                logger.warn("md5 error pac." + md5Result.reason);
                getTracer().traceMD5Error(key, content, recvTimeSec);
                response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                        "2. data md5 error,bacause of : " + md5Result.reason);
                return;
            }
            logger.debug(key + " md5 test parsed.");

            //parse to json
            JSONObject obj;
            try {
                obj = JSONObject.parseObject(content);
            } catch (Exception e) {
                logger.warn("json parse error. " + ExceptionUtils.getStackTrace(e));
                if (getTracer() != null) {
                    getTracer().traceJsonParseError(key, content, recvTimeSec);
                }
                response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                        "3. content cannot be parsed to json.");
                return;
            }
            logger.debug(key + " pac parsed to json.");

            // get msg type
            String type = obj.getString("msg_type");
            // get msg version
            String version = obj.getString("msg_version");
            // check key
            if (getKeeper() != null) {
                if (getOnline()) {
                    if (!(keeper.isOnline(key) || keeper.isOnline(key + type))) {
                        logger.debug(key + " key not online.");
                        response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                                "4. key not online");
                        return;
                    }
                } else if (!(keeper.isTest(key) || keeper.isTest(key + type))) {
                    logger.debug(key + " key not test.");
                    response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                            "4. key not test");
                    return;
                }
                if (!(keeper.isActivated(key) || keeper.isActivated(key + type))) {
                    logger.debug(key + " key not activated.");
                    response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                            "4. key not activated");
                    return;
                }
            } else {
                logger.info("keeper is null");
            }

            // check fields
            logger.debug(key + " start check fields.");
            // check msg_type
            if (type == null) {
                obj.put("err_msg", "msg_type not exist.");
                logger.debug(key + "msg type not exist.");
                if (getTracer() != null) {
                    getTracer().traceJsonParseError(key, obj.toJSONString(), recvTimeSec);
                }
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "5. msg_type not exist.");
                return;
            }
            // record_list empty
            JSONArray recs = obj.getJSONArray("record_list");
            if (recs != null && recs.isEmpty()) {
                logger.debug(key + " empty.");
                try {
                    if (getTracer() != null) {
                        getTracer().traceCorrect(key, obj, recs, recs.size(), recvTimeSec);
                    }
                    response.setStatus(HttpServletResponse.SC_OK);
//				response.sendError(HttpServletResponse.SC_BAD_REQUEST, "record_list not exist.");
                    return;
                } catch (NullPointerException e) {
                    if (null == key) {
                        logger.error("===============key is null");
                    }
                    if (null == obj) {
                        logger.error("===============obj is null");
                    }
                    if (null == recs) {
                        logger.error("===============recs is null");
                    }
                    if (recs.size() == 0) {
                        logger.error("===============recs.size() is null");
                    }
                    if (recvTimeSec == 0) {
                        logger.error("===============recvTimeSec is null");
                    }
                    response.sendError(HttpServletResponse.SC_BAD_REQUEST, "7. unknow error.because:" + ExceptionUtils.getStackTrace(e));
//                    logger.error("=======================key:" + key + " obj:" + obj + " recs:" + recs + " recs.size():" + recs.size() + " recvTimeSec:" + recvTimeSec + ExceptionUtils.getStackTrace(e));
                } catch (Exception e) {
                    response.sendError(HttpServletResponse.SC_BAD_REQUEST, "7. unknow error.because:" + ExceptionUtils.getStackTrace(e));
                    logger.error(ExceptionUtils.getStackTrace(e));
                }

            }
            // check each record
            // has record_list
            int errorCount = 0;
            int recId = 0;
            String errorString = "";
            JSONArray traceRecs = new JSONArray();
            for (Object recO : recs) {
                ++recId;
                JSONObject rec = (JSONObject) recO;
                CheckResult result = getChecker().check(key + "_" + version + "_" + type, rec);
                JSONObject traceRec = new JSONObject(rec);
                if (!result.correct) {
                    logger.error(key + "_" + version + "_" + type + " " + recId + "th record error. " + result.reason);
                    ++errorCount;
                    traceRec.putAll(JSONObject.parseObject(result.reason));
                    if (errorString.isEmpty()) {
                    } else {
                        errorString += ",";
                    }
                    errorString += recId + "th rec: " + result.reason;
                }
                traceRecs.add(traceRec);
            }
            logger.debug(key + " check fields finished.");
            // trace first
            if (getTracer() != null) {
                logger.debug(key + " start trace");
                if (errorCount > 0) {
                    logger.debug(key + " trace json check error.");
                    getTracer().traceJsonCheckError(key, obj, traceRecs,
                            recs.size(), errorCount, recvTimeSec);
                } else {
                    logger.debug(key + " trace correct.");
                    getTracer().traceCorrect(key, obj, traceRecs, recs.size(), recvTimeSec);
                }
                logger.debug(key + " trace finished.");
            }
            logger.debug(key + " error_count=" + errorCount + ", error_string=" + errorString + ", trace_recs=" + traceRecs.toJSONString());
            // if error exist
            if (!errorString.isEmpty()) {
                logger.debug(key + " returning error info.");
                try {
                    response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                            "5. {" + errorString + "}");
                } catch (Exception e) {
                    logger.error("err msg: {" + errorString + "}", e);
                }
                return;
            }
            monitor.inc(key + " " + type, recs.size());
            monitor.inc("total " + type, recs.size());

            obj.put("key", key);
            // append to file
            if (getFileAppender() != null) {
                getFileAppender().append(obj.toJSONString());
            }

            // send to rmq
            if (getSender() != null) {
                try {
                    getSender().sendMessage(obj, recvTimeSec);
                } catch (RESessionException | REConnectionException ex) {
                    logger.error("cannot build rmq sender.", ex);
                    response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                            "6. data send error.because:" + ExceptionUtils.getStackTrace(ex));
                    return;
                } catch (NoSuchDataTypeException ex) {
                    logger.error("data type error.", ex);
                    response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                            "6. data type error.because:" + ExceptionUtils.getStackTrace(ex));
                    return;
                }
            }
//		response.getOutputStream().println("success");
            response.setStatus(HttpServletResponse.SC_OK);
        } catch (Exception | Error e) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "7. unknow error.because:" + ExceptionUtils.getStackTrace(e));
            logger.error(ExceptionUtils.getStackTrace(e),e);
        }
    }
}
